package cn.wizzer.app.user.modules.service.impl;

import cn.wizzer.app.user.modules.models.UserAccountRecord;
import cn.wizzer.app.user.modules.models.UserAccountRecordStatistics;
import cn.wizzer.app.user.modules.models.UserTransfer;
import cn.wizzer.app.user.modules.models.enums.BizTypeEnum;
import cn.wizzer.app.user.modules.models.enums.TransferStatusEnum;
import cn.wizzer.app.user.modules.models.vo.AccountRecordStatisticsVo;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordService;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordStatisticsService;
import cn.wizzer.app.user.modules.services.account.UserTransferService;
import cn.wizzer.framework.base.Result;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.base.service.CommonDBService;
import com.alibaba.dubbo.config.annotation.Service;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author zl
 * @date 2020/6/17 9:59
 */
@IocBean(args = {"refer:dao"})
@Service(interfaceClass = UserAccountRecordStatisticsService.class)
public class UserAccountRecordStatisticsServiceImpl extends BaseServiceImpl<UserAccountRecordStatistics> implements UserAccountRecordStatisticsService {
    public UserAccountRecordStatisticsServiceImpl(Dao dao) {
        super(dao);
    }

    @Inject
    private UserAccountRecordService userAccountRecordService;
    @Inject
    private UserTransferService userTransferService;
    @Inject
    private CommonDBService dbService;

    /**
     * 流水统计
     */
    @Aop(TransAop.READ_COMMITTED)
    @Override
    public synchronized void statistics() {
        //查询未统计的用户流水，最多一千条
        List<UserAccountRecord> userAccountRecords = userAccountRecordService
                .listEntity(Sqls.create("SELECT id, user_id, ABS(operate_amount) operate_amount, biz_type, ctAt " +
                        "FROM user_account_record " +
                        "WHERE counted = 0 AND delFlag = 0 " +
                        "LIMIT 1000"));
        //查询未统计的用户收款记录
        List<UserTransfer> userTransfers = userTransferService.query(Cnd.where("status", "=", TransferStatusEnum.RECEIVED).and("counted", "=", 0).and("delFlag", "=", 0));
        Map<String, UserAccountRecordStatistics> map = new HashMap<>();
        //待更新统计状态集合
        List<String> updateAccountIds = new ArrayList<>();
        //用户流水按创建时间（日期）与用户进行分组统计(无收款类型流水)
        for (UserAccountRecord userAccountRecord : userAccountRecords) {
            String date = Times.format("yyyy-MM-dd", Times.D(userAccountRecord.getCtAt()));
            String userId = userAccountRecord.getUserId();
            String key = date + userId;
            UserAccountRecordStatistics statistics = map.get(key);
            if (statistics == null) {
                //先去数据库查查记录是否存在
                statistics = this.fetch(Cnd.where("date", "=", date).and("userId", "=", userId));
                //若记录不存在则初始化流水统计
                if (statistics == null) {
                    //初始化流水统计
                    statistics = this.insert(UserAccountRecordStatistics.init(userId, date));
                    statistics = this.fetch(statistics.getId());
                }
                map.put(key, statistics);
            }
            //统计记录按类型添加金额
            Boolean result = addAmount(statistics, userAccountRecord.getBizType(), userAccountRecord.getOperateAmount());
            if (result) {
                //添加入待更新统计状态集合中
                updateAccountIds.add(userAccountRecord.getId());
            }
        }
        //收款记录按操作时间（日期）与收款用户进行分组
        for (UserTransfer userTransfer : userTransfers) {
            String date = Times.format("yyyy-MM-dd", Times.D(userTransfer.getOpAt() * 1000L));
            String userId = userTransfer.getCollectUserId();
            String key = date + userId;
            UserAccountRecordStatistics statistics = map.get(key);
            if (statistics == null) {
                //先去数据库查查记录是否存在
                statistics = this.fetch(Cnd.where("date", "=", date).and("userId", "=", userId));
                //若记录不存在则初始化流水统计
                if (statistics == null) {
                    //初始化流水统计
                    statistics = this.insert(UserAccountRecordStatistics.init(userId, date));
                    statistics = this.fetch(statistics.getId());
                }
                map.put(key, statistics);
            }
            statistics.setCollect(statistics.getCollect()
                    .add(userTransfer.getAmount()).setScale(2, RoundingMode.DOWN));
        }
        //更新流水统计状态
        userAccountRecordService.update(Chain.make("counted", 1), Cnd.where("id", "IN", updateAccountIds));
        //更新收款记录统计状态
        List<String> transferIds = userTransfers.stream().map(UserTransfer::getId).collect(Collectors.toList());
        userTransferService.update(Chain.make("counted", 1), Cnd.where("id", "IN", transferIds));

        //流水统计数据更新
        for (UserAccountRecordStatistics statistics : map.values()) {
            //计算系统收入支出
            calculateSystemAmount(statistics);
            this.update(statistics);
        }
    }

    /**
     * 统计记录按类型添加金额
     *
     * @param statistics
     * @param bizType
     * @param amount
     */
    public Boolean addAmount(UserAccountRecordStatistics statistics, BizTypeEnum bizType, BigDecimal amount) {
        switch (bizType) {
            case CASH:
                statistics.setCash(statistics.getCash()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case RECHARGE:
                statistics.setRecharge(statistics.getRecharge()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case GIFT_ORDER:
                statistics.setGiftOrder(statistics.getGiftOrder()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case GIFT_INCOME:
                statistics.setGiftIncome(statistics.getGiftIncome()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case BALL_BET:
                statistics.setBallBet(statistics.getBallBet()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case BALL_REVERT:
                statistics.setBallRevert(statistics.getBallRevert()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case BALL_RESULT:
                statistics.setBallResult(statistics.getBallResult()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case EXCHANGE:
                statistics.setExchange(statistics.getExchange()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case EXCHANGE_BACK:
                statistics.setExchangeBack(statistics.getExchangeBack()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case TRANSFER:
                statistics.setTransfer(statistics.getTransfer()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case TRANSFER_CANCEL:
                statistics.setTransferCancel(statistics.getTransferCancel()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
//            case COLLECT:
//                statistics.setCollect(statistics.getCollect()
//                        .add(amount).setScale(2, RoundingMode.DOWN));
//                break;
            case SELF_REBATE:
                statistics.setSelfRebate(statistics.getSelfRebate()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case SUB_REBATE:
                statistics.setSubRebate(statistics.getSubRebate()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case BUY_VIEW_POINT:
                statistics.setBuyViewPoint(statistics.getBuyViewPoint()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case REFUND_VIEW_POINT:
                statistics.setRefundViewPoint(statistics.getRefundViewPoint()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case SELL_VIEW_POINT:
                statistics.setSellViewPoint(statistics.getSellViewPoint()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case RETURN_VIEW_POINT:
                statistics.setReturnViewPoint(statistics.getReturnViewPoint()
                        .add(amount).setScale(2, RoundingMode.DOWN));
                break;
            case ACTIVITY_COST_REBATE:
                statistics.setActivityCostRebate(statistics.getActivityCostRebate()
                        .add(amount).setScale(2, RoundingMode.DOWN));
            case ACTIVITY_INVITE_REBATE:
                statistics.setActivityInviteRebate(statistics.getActivityInviteRebate()
                        .add(amount).setScale(2, RoundingMode.DOWN));
            default:
                //若类型不对则不统计，不更改状态
                return false;
        }
        return true;
    }

    /**
     * 计算系统收入支出
     *
     * @param statistics
     */
    public void calculateSystemAmount(UserAccountRecordStatistics statistics) {
        //系统礼物收入 = 礼物支出 - 礼物收入
        statistics.setSystemGiftIncome(statistics.getGiftOrder().subtract(statistics.getGiftIncome()));
        //系统返水支出 = 自身返利 + 下级返水
        statistics.setSystemRebatePay(statistics.getSelfRebate().add(statistics.getSubRebate()));
        //游戏中心盈亏 = 球类下注 - 球注撤销 - 球注结算 - 系统返水支出
        statistics.setGameCenterIncome(statistics.getBallBet()
                .subtract(statistics.getBallRevert())
                .subtract(statistics.getBallResult())
                .subtract(statistics.getSystemRebatePay()));
        //商品支出 = 兑换 - 兑换退还
        statistics.setGoodsPay(statistics.getExchange().subtract(statistics.getExchangeBack()));
        //系统观点收入 = 购买观点 - 出售观点 - 观点退款 + 观点退还
        statistics.setSystemViewPointIncome(statistics.getBuyViewPoint()
                .subtract(statistics.getSellViewPoint())
                .subtract(statistics.getRefundViewPoint())
                .add(statistics.getReturnViewPoint()));
    }

    /**
     * 分页查询用户流水统计
     *
     * @param searchKeyword
     * @param searchDate
     * @param pageOrderName
     * @param pageOrderBy
     * @param pageNumber
     * @param pageSize
     * @return
     */
    @Override
    public Map listPage(String searchKeyword, String[] searchDate, String pageOrderName,
                        String pageOrderBy, int pageNumber, int pageSize) {
        Sql sql = Sqls.create("SELECT s.*, u.userName, u.nickName, u.phone " +
                "FROM user_account_record_statistics s ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM user_account_record_statistics s ");
        Sql joinSql = Sqls.create("LEFT JOIN user u ON s.userId = u.id " +
                "$condition ");
        Cnd cnd = Cnd.where("s.delFlag", "=", 0);
        if (Strings.isNotBlank(searchKeyword)) {
            searchKeyword = "%" + searchKeyword + "%";
            cnd.and(Cnd.exps("u.nickName", "LIKE", searchKeyword)
                    .or("u.phone", "LIKE", searchKeyword)
                    .or("u.userName", "LIKE", searchKeyword));
        }
        if (searchDate != null && searchDate.length == 2) {
            cnd.and("s.date", ">=", searchDate[0]);
            cnd.and("s.date", "<=", searchDate[1]);
        }
        String sort;
        if (Strings.isNotBlank(pageOrderName) && Strings.isNotBlank(pageOrderBy)) {
            sort = "ORDER BY s." + pageOrderName + " " + pageOrderBy;
        } else {
            sort = "ORDER BY s.date DESC";
        }
        joinSql.setCondition(cnd);
        sql.appendSourceSql(joinSql.toString());
        sql.appendSourceSql(sort);
        countSql.appendSourceSql(joinSql.toString());
        return Result.createMap("pagination", dbService.listPage(AccountRecordStatisticsVo.class, pageNumber, pageSize, sql.toString(), countSql.toString()),
                "sumMap", systemIncomeStatistics(searchKeyword, searchDate));
    }

    /**
     * 系统收益统计
     *
     * @param searchKeyword
     * @param searchDate
     * @return
     */
    public Map systemIncomeStatistics(String searchKeyword, String[] searchDate) {
        Sql sql = Sqls.create("SELECT SUM(s.recharge) recharge, SUM(s.cash) cash, SUM(s.giftOrder) giftOrder, SUM(s.giftIncome) giftIncome, " +
                "SUM(s.systemGiftIncome) systemGiftIncome, SUM(s.ballBet) ballBet, SUM(s.ballRevert) ballRevert, SUM(s.ballResult) ballResult, " +
                "SUM(s.selfRebate) selfRebate, SUM(s.subRebate) subRebate, SUM(s.systemRebatePay) systemRebatePay, SUM(s.gameCenterIncome) gameCenterIncome, " +
                "SUM(s.exchange) exchange, SUM(s.exchangeBack) exchangeBack, SUM(s.goodsPay) goodsPay, SUM(s.transfer) transfer, " +
                "SUM(s.transferCancel) transferCancel, SUM(s.collect) collect, SUM(s.buyViewPoint) buyViewPoint, SUM(s.sellViewPoint) sellViewPoint, " +
                "SUM(s.refundViewPoint) refundViewPoint, SUM(s.returnViewPoint) returnViewPoint, SUM(s.systemViewPointIncome) systemViewPointIncome, " +
                "SUM(s.activityCostRebate) activityCostRebate, SUM(s.activityInviteRebate) activityInviteRebate " +
                "FROM user_account_record_statistics s " +
                "LEFT JOIN user u ON s.userId = u.id " +
                "WHERE s.delFlag = 0 ");
        if (Strings.isNotBlank(searchKeyword)) {
            sql.appendSourceSql("AND (u.userName LIKE CONCAT('%', @searchKeyword, '%') " +
                    "OR u.nickName LIKE CONCAT('%', @searchKeyword, '%') " +
                    "OR u.phone LIKE CONCAT('%', @searchKeyword, '%') )");
            sql.setParam("searchKeyword", searchKeyword);
        }
        if (searchDate != null && searchDate.length == 2) {
            sql.appendSourceSql("AND s.date BETWEEN @startTime AND @endTime");
            sql.setParam("startTime", searchDate[0]);
            sql.setParam("endTime", searchDate[1]);
        }
        sql.setCallback(Sqls.callback.map());
        this.execute(sql);
        return sql.getObject(Map.class);
    }

}
